package	model.code;

import lang.Language;
import model.Project;
import model.Segment;
import model.code.line.LineList;
import model.code.line.MultiLineList;
import model.code.line.NameLine;
import model.symbol.Scope;
import model.symbol.Symbol;
import observer.Copiable;

/**
 * Subclasses of this class are for representing pieces of {@link CodeFile}, including entire {@link CodeFile{s.
 *
 * @author Neil Dickson
 */
public abstract class CodeFileChunk implements Copiable<CodeFileChunk>, Symbol {
	private final	long			id;
	private			Section			parent;
	protected		NameLine		name;
	protected		MultiLineList	description;

	// NOTE: If the order of these LOCATION_* constants changes, getNextLineList() and getPrevLineList() may need updating.
	public static final int LOCATION_NOWHERE			= -1;
	public static final int LOCATION_NAME				= 0;
	public static final int LOCATION_DESCRIPTION		= 1;
	public static final int LOCATION_PARAMETERS			= 2;
	/**
	 * Same as {@link #LOCATION_PARAMETERS}, because parameters won't be shown for test function.
	 */
	public static final int LOCATION_TEST_INFO			= 2;
	public static final int LOCATION_LOCAL_VARS			= 3;
	public static final int LOCATION_RETURNS			= 4;
	public static final int LOCATION_MODIFIERS			= 5;
	public static final int LOCATION_INCREMENT			= 6;
	public static final int LOCATION_SEGMENT			= 7;
	public static final int LOCATION_CONSTANTS_MEMBERS	= 8;
	public static final int LOCATION_STRUCTURE_MEMBERS	= 9;
	public static final int LOCATION_VAR_PIECES			= 10;
	public static final int LOCATION_SEE_ALSO			= 11;
	public static final int LOCATION_AUTHORS			= 12;
	public static final int LOCATION_CODE_PANEL			= 13;
	public static final int LOCATION_CONTENT_PANEL		= 14;
	public static final int LOCATION_MAX				= 14;

	public static final int DEFAULT_ALIGNMENT_MARKER	= 0;
	public static final int DEFAULT_FUNCTION_ALIGNMENT	= 16;
	public static final int DEFAULT_VARIABLE_ALIGNMENT	= 4;
	
	public static final String[]	ACCESS_SCOPE_TYPES	= new String[]{"","global","file"};

	protected CodeFileChunk(String name, String description) {
		id = Scope.getUniqueID();
		this.name = new NameLine(name.toCharArray(),this);
		this.description = new MultiLineList(this,LOCATION_DESCRIPTION);
		setDescription(description);
	}

	public NameLine getName() {
		return name;
	}
	public MultiLineList getDescription() {
		return description;
	}

	/**
	 * Sets the description text and updates observers.
	 * @param description the new description of this {@link CodeFileChunk}
	 */
	public void setDescription(String description) {
		this.description.replaceAll(description);
	}

	public String toString() {
		return name.toString();
	}

	public Section getParent() {
		return parent;
	}

	public Project getProject() {
		CodeFile file = getFile();
		if (file!=null) {
			return file.getProject();
		}
		return null;
	}

	public CodeFile getFile() {
		if (this instanceof CodeFile) {
			return (CodeFile)this;
		}
		CodeFileChunk chunk = parent;
		while (chunk!=null && !(chunk instanceof CodeFile)) {
			chunk = chunk.getParent();
		}
		if (chunk==null) {
			return null;
		}
		return (CodeFile)chunk;
	}

	public Language getLanguage() {
		final CodeFile file = getFile();
		if (file==null) {
			return null;
		}
		return file.getLanguage();
	}

	public void setParent(Section parent) {
		this.parent = parent;
	}

	public long getID() {
		return id;
	}

	public Scope getGlobalScope() {
		if (parent!=null) {
			return parent.getGlobalScope();
		}
		return null;
	}

	/**
	 * NOTE: This function is overridden by subclasses that have their own scope.
	 * @return the lowest level scope of this {@link CodeFileChunk}
	 */
	public Scope getScope() {
		return getGlobalScope();
	}

	public Segment getSegment() {
		Section parent = getParent();
		Project project = getProject();
		if (parent==null || parent instanceof CodeFile || parent.getSegmentName().length()==0) {
			if (project==null) {
				return null;
			}
			if (this instanceof Function) {
				return project.getDefaultSegment(Segment.CODE);
			}
			if (this instanceof GlobalVariable) {
				return project.getDefaultSegment(Segment.INIT_DATA);
			}
			return project.getDefaultSegment(Segment.CODE);
		}
		return project.getSegment(parent.getSegmentName().toString());
	}

	public int getMode() {
		Segment segment = getSegment();
		if (segment==null) {
			return Segment.DEFAULT_N_BITS;
		}
		return segment.getnBits().get();
	}

	/**
	 * This adds any global {@link model.symbol.Symbol}s of chunk to the global {@link Scope}.
	 * Any that are already added won't get added twice because Scope.add() checks first.
	 * @param symbol the symbol to be added to global scope (along with any child chunks)
	 */
	protected void addToGlobalScope(Symbol symbol) {
		Scope globalScope = getGlobalScope();
		if (globalScope!=null) {
			if (symbol instanceof Section) {
				globalScope.addAll(((Section)symbol).getAllGlobalSymbols());
			}
			else if (symbol instanceof Function || symbol instanceof Structure || symbol instanceof GlobalVariable) {
				globalScope.add(symbol);
			}
			else if (symbol instanceof Constants) {
				globalScope.addAll(((Constants)symbol).getAllGlobalSymbols());
			}
		}
	}

	/**
	 * This removes any global {@link model.symbol.Symbol}s of chunk from the global {@link Scope}.
	 * @param chunk the chunk to be removed from global scope (along with any child chunks)
	 */
	protected void removeFromGlobalScope(CodeFileChunk chunk) {
		Scope globalScope = getGlobalScope();
		if (globalScope!=null) {
			if (chunk instanceof Section) {
				globalScope.removeAll(((Section)chunk).getAllGlobalSymbols());
			}
			else if (chunk instanceof Function) {
				globalScope.remove((Function)chunk);
			}
			else if (chunk instanceof Structure) {
				globalScope.remove((Structure)chunk);
			}
			else if (chunk instanceof GlobalVariable) {
				globalScope.remove((GlobalVariable)chunk);
			}
			else if (chunk instanceof Constants) {
				globalScope.removeAll(((Constants)chunk).getAllGlobalSymbols());
			}
		}
	}

	public LineList getLineList(int locType, int locIndex) {
		if (locType==LOCATION_NAME) {
			return name;
		}
		else if (locType==LOCATION_DESCRIPTION) {
			return description;
		}
		else {
			return null;
		}
	}
	public LineList getFirstLineList() {
		return name;
	}
	public LineList getNextLineList(LineList list) {
		int locType = list.getLocationType();
		int locIndex = list.getLocationIndex();
		if (locType==LOCATION_NAME) {
			return description;
		}
		return null;
	}
	public LineList getPrevLineList(LineList list) {
		int locType = list.getLocationType();
		int locIndex = list.getLocationIndex();
		if (locType==LOCATION_DESCRIPTION) {
			return name;
		}
		return null;
	}
	public LineList getLastLineList() {
		return description;
	}
	public static LineList getNextLineListPlus(LineList list) {
		CodeFileChunk chunk = list.getParent();
		LineList next = chunk.getNextLineList(list);
		if (next!=null) {
			return next;
		}
		if (chunk instanceof Section && ((Section)chunk).size()>0) {
			return ((Section)chunk).get(0).getFirstLineList();
		}
		Section parent = chunk.getParent();
		if (parent!=null) {
			int index = parent.indexOf(chunk);
			if (index+1<parent.size()) {
				return parent.get(index+1).getFirstLineList();
			}
			chunk = parent;
			parent = chunk.getParent();
			if (parent!=null) {
				index = parent.indexOf(chunk);
				if (index+1<parent.size()) {
					return parent.get(index+1).getFirstLineList();
				}
			}
		}
		return null;
	}
	public static LineList getPrevLineListPlus(LineList list) {
		CodeFileChunk chunk = list.getParent();
		LineList prev = chunk.getPrevLineList(list);
		if (prev!=null) {
			return prev;
		}
		Section parent = chunk.getParent();
		if (parent!=null) {
			int index = parent.indexOf(chunk);
			if (index>0) {
				CodeFileChunk prevChunk = parent.get(index-1);
				if (prevChunk instanceof Section) {
					if (((Section)prevChunk).size()>0) {
						prevChunk = ((Section)prevChunk).getLast();
					}
				}
				return prevChunk.getLastLineList();
			}
			return parent.getLastLineList();
		}
		return null;
	}
}
